package com.ssm.security;

import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * 实现基于请求头的 AuthenticationProvider
 * 负责校验我们自定义 Authentication token (SignedUsernamePasswordAuthenticationToken)的签名
 */
public class SignedUsernamePasswordAuthenticationProvider extends DaoAuthenticationProvider {
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    /* (non-Javadoc)
         * @see org.springframework.security.authentication.dao1.AbstractUserDetailsAuthenticationProvider#supports(java.lang.Class)
         *
         */

    /**
     * 重写父类的方法,向 AuthenticationManager 指明当前 AuthenticationProvider 要进行校验的期望运行时 Authentication token
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<? extends Object> authentication) {
        return (SignedUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }

    /* (non-Javadoc)
     * @see org.springframework.security.authentication.dao1.DaoAuthenticationProvider#additionalAuthenticationChecks(org.springframework.security.core.userdetails.UserDetails, org.springframework.security.authentication.UsernamePasswordAuthenticationToken)
     */

    /**
     * additionalAuthenticationChecks 方法被父类调用，此方法允许子类对 token 进行特有的校验。这正适合我们的策略，所以添加上我们对 token 新的签名检查
     * @param userDetails
     * @param authentication
     * @throws AuthenticationException
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {
        super.additionalAuthenticationChecks(userDetails, authentication);

        SignedUsernamePasswordAuthenticationToken signedToken =(SignedUsernamePasswordAuthenticationToken) authentication;

        if(signedToken.getRequestSignature() == null) {
            throw new BadCredentialsException(this.messages.getMessage("SignedUsernamePasswordAuthenticationProvider.missingSignature", "Missing request signature"),userDetails);
        }

        // calculate expected signature
        if(!signedToken.getRequestSignature().equals(calculateExpectedSignature(signedToken))) {
            throw new BadCredentialsException(this.messages.getMessage("SignedUsernamePasswordAuthenticationProvider.badSignature", "Invalid request signature"),userDetails);
        }
    }

    /**
     * Given a signed token, calculates the signature value expected to be suppliled.
     *
     * @param signedToken the signed token to evaluate
     * @return the expected signature
     */
    private String calculateExpectedSignature(SignedUsernamePasswordAuthenticationToken signedToken) {
        return signedToken.getPrincipal() + "|+|" + signedToken.getCredentials();
    }
}
